import Button from "@/component/Button";
import { Flex } from "@/component/Flex";
import { Input } from "@/component/Input";
import { useConfirmModal } from "@/component/Modal";
import Show from "@/component/Show";
import { Text } from "@/gimd/Text";
import useToast, { Toast } from "@/component/Toast";
import TopBar, { ITopBarItem } from "@/component/TopBar";
import { getArticleDetail } from "@/hooks/useArticleDetail";
import useLocationParams from "@/hooks/useLocationParams";
import { ApiHttp } from "@/network/ApiHttp";
import { DraftsStore } from "@/store/DraftsStore";
import { GlobalStore } from "@/store/GlobalStore";
import "@/styles/vditor/index.css";
import { MD5 } from "crypto-js";
import devices from 'current-device';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useLocation, useNavigate } from 'react-router';
import { v4 as uuid } from "uuid";
import Vditor from 'vditor';
import { DraftListView } from "./DraftListView";
import { VditorView } from "./VditorView";
import { IDraftDetail, IDraftItem, PwdNotMatchError as PasswordNotMatchError } from "./draftsMgr";
import styles from './style.module.less';
import { useDrafts } from "./useDrafts";
import { useOSS } from "./useOSS";

function useInit(r: Pick<ReturnType<typeof useDrafts>, 'reload' | 'create' | 'save'>): any {
  const [, dispatch] = DraftsStore.useStore();
  const { pathname } = useLocation()
  const navigate = useNavigate();
  const { create, reload, save } = r
  const { str } = useLocationParams();
  const draftId = str('draft_id')
  const articleId = str('article_id')
  const draftIdRef = useRef<string | undefined>(void 0)
  const [error, setError] = useState<any>();

  useEffect(() => {
    if (!articleId) return;
    navigate(pathname, { replace: true })
    const ac = new AbortController()
    getArticleDetail(articleId, ac)
      .then(v =>
        v.draft_id ?
          save({
            draft_id: v.draft_id,
            draft_title: v.title,
            draft_content: v.content,
            draft_breif: v.breif,
            article_id: articleId,
            unique_pwd: ''
          }) :
          create({ draft_title: v.title, draft_content: v.content, draft_breif: v.breif, article_id: articleId, draft_id: v.draft_id })
      ).then(draft => {
        dispatch({ type: 'last_draft_id', payload: draft.draft_id })
      }).then(() => {
        return reload()
      }).catch(setError)
    return () => ac.abort()
  }, [reload, create, dispatch, navigate, pathname, articleId, save])

  useEffect(() => {
    if (draftIdRef.current === draftId) return;
    draftIdRef.current = draftId
    if (!draftId) return;

    dispatch({ type: 'last_draft_id', payload: draftId });
    navigate(pathname, { replace: true })
  }, [dispatch, navigate, pathname, draftId])

  return error;
}

export default function BlogEditor() {
  const ref_oss = useOSS();
  const [toast, toastCtx, useAutoToast] = useToast();
  const navigate = useNavigate();
  const {
    loading: drafts_loading,
    create: _create_draft,
    remove: remove_draft,
    reload: _reload_drafts,
    save: save_draft,
    get: get_draft,
    list: drafts_list,
    saving: draft_saving
  } = useDrafts();

  const create_draft = useCallback(async () => {
    return _create_draft();
  }, [_create_draft]);

  const reload_drafts = useCallback(async () => {
    const list = await _reload_drafts()
    if (list.length) return list;
    await create_draft();
    return _reload_drafts();
  }, [_reload_drafts, create_draft])

  const [{ username }] = GlobalStore.useStore();
  const [{ last_draft_id: draft_id, draft_visible }, dispatch] = DraftsStore.useStore();

  const ref_editor = useRef<Vditor>();

  const ref_draft_detail = useRef<IDraftDetail | null>(null)
  const ref_ele_input_title = useRef<HTMLInputElement>(null);
  const ref_ele_input_unique_pwd = useRef<HTMLInputElement>(null);

  const [is_editor_ready, set_editor_ready] = useState<boolean>(false);
  const [is_title_changed, set_is_title_changed] = useState<boolean>(false);
  const [is_u_pwd_changed, set_is_u_pwd_changed] = useState<boolean>(false);

  const initError = useInit({ reload: reload_drafts, create: create_draft, save: save_draft })

  useAutoToast(initError);
  const setDraftsVisible = (v: boolean) => dispatch({ type: 'draft_visible', payload: v })

  const create_article_form = useCallback(() => {
    const vditor = ref_editor.current;
    const ele_input_title = ref_ele_input_title.current
    if (!vditor || !ele_input_title || !is_editor_ready)
      return void 0;

    const div = document.createElement('div')
    div.innerHTML = vditor.getHTML()
      // .replace(/<img.*?src="(.*?)".*?>/g, (r, url) => `<a href="${url}" onclick="on_click_img(this); event.preventDefault(); return false;">[图片]<a/>`)
      .replace(/<video.*?>/g, '[视频]')
      .replace(/<audio.*?>/g, '[音频]')
      .replace(/<pre>(\s|\S)*?<\/pre>/g, '<br>[代码块]<br>')
      .replace(/<hr>/g, '<br>')
      .replace(/<h(1|2|3|4|5|6)>/g, (_, [r]) => `<br><div>[${r}级标题:`)
      .replace(/<\/h(1|2|3|4|5|6)>/g, ']</div>')

    const breif = div.innerHTML
    const ret = {
      title: ele_input_title.value.trim(),
      content: vditor.getValue(),
      html: vditor.getHTML(),
      breif: breif.length >= 753 ? breif.substring(0, 750) + '...' : breif
    }
    return ret
  }, [is_editor_ready])

  const open_draft = useCallback(async (next_draft: IDraftItem) => {
    dispatch({ type: 'last_draft_id', payload: next_draft.draft_id });
  }, [dispatch])

  const save_current_draft = useCallback(async () => {
    if (!draft_id) throw Error("draft_id cant be void");
    const form = create_article_form()
    if (!form) throw Error("edtior not ready yet");

    return save_draft({
      ...ref_draft_detail.current,
      draft_id,
      draft_title: form.title,
      draft_content: form.content,
      draft_breif: form.breif,
      unique_pwd: ref_ele_input_unique_pwd.current!.value
    })
  }, [draft_id, create_article_form, save_draft])

  const remove_current_draft = () => {
    if (!draft_id) return;
    return remove_draft(draft_id)
      .then(n => n || create_draft())
      .then(draft => open_draft(draft))
      .then(() => reload_drafts())
      .then(() => toast('草稿已删除'))
      .catch(e => {
        toast('' + e);
        return false
      })
  }
  const [showConfirmModalOfDelete, confirmModalOfDeleteCtx] = useConfirmModal({
    children: "确定删除当前草稿？",
    onOk: remove_current_draft
  })

  const on_click_remove_current_draft = () => showConfirmModalOfDelete()
  const on_click_save_current_draft = useCallback(() => save_current_draft()
    .then(_ => toast(`草稿已保存`))
    .catch(e => toast('' + e)), [save_current_draft, toast])
  const on_click_create_draft = useCallback(async () => {
    return on_click_save_current_draft()
      .then(() => create_draft())
      .then(draft => open_draft(draft))
      .catch(e => toast('' + e))
  }, [create_draft, on_click_save_current_draft, open_draft, toast]);


  const exit = async () => {
    if (draft_id) await save_current_draft();
    navigate('/', { replace: true })
  }

  const [show_confirm_modal_of_exit, confirm_modal_of_exit_ctx] = useConfirmModal({
    children: "确定退出？",
    onOk: (e) => {
      e.stopPropagation();
      e.preventDefault();
      exit();
    }
  })
  const on_click_exit = () => show_confirm_modal_of_exit()
  const on_editor_ready = (v: Vditor) => {
    ref_editor.current = v
    set_editor_ready(true)
  }
  const on_click_publish_draft_to_article = useCallback(async () => {
    try {
      const form = create_article_form()
      if (!form) return;
      const { title, content, breif, html } = form;

      if (!title.trim()) return toast("发布失败。标题不能为空")
      if (!content.trim()) return toast("发布失败。内容不能为空")
      const _oss = ref_oss.current
      if (!_oss) return;
      if (/[\\\\/:*?"<>|]/.test(title))
        return toast("发布失败。标题不能包含以下【\\\\/:*?\"<>|】字符,请修改")

      const _draft_id: string = draft_id || await create_draft().then(v => v.draft_id);
      const unique_pwd = ref_ele_input_unique_pwd.current?.value;
      save_draft({
        ...ref_draft_detail.current,
        draft_id: _draft_id,
        draft_title: title,
        draft_content: content,
        draft_breif: breif,
        unique_pwd: unique_pwd
      })
      const { data: { salt } } = await ApiHttp.post('/blog/prepare');
      const username_hash = MD5(salt + username).toString()
      const { url: link } = await _oss.put(`blog/articles/${username_hash}/md/${MD5(salt + content)}`, new Blob([content]), {
        mime: "text/x-markdown",
        headers: {
          "Content-Type": `text/x-markdown; charset=utf-8`,
          "Content-Disposition": `attachment;filename="${encodeURIComponent(title)}.md"`,
          "x-oss-meta-username": GlobalStore.value.username,
          "x-oss-meta-draft-id": _draft_id,
        },
      });
      const { url: html_link } = await _oss.put(`blog/articles/${username_hash}/html/${MD5(salt + html)}`, new Blob([html]), {
        mime: "text/html",
        headers: {
          "Content-Type": `text/html; charset=utf-8`,
          "Content-Disposition": `attachment;filename="${encodeURIComponent(title)}.md"`,
          "x-oss-meta-username": GlobalStore.value.username,
          "x-oss-meta-draft-id": _draft_id,
        },
      });
      const { data: article_id } = await ApiHttp.post('/blog/save', void 0, {
        id: ref_draft_detail.current?.article_id,
        title,
        breif,
        draft_id: _draft_id,
        link,
        html_link
      });
      save_draft({
        ...ref_draft_detail.current,
        draft_id: _draft_id,
        draft_title: title,
        draft_content: content,
        draft_breif: breif,
        article_id: article_id,
        unique_pwd: ref_ele_input_unique_pwd.current!.value
      })
      toast("发布成功")
    } catch (e) {
      toast('' + e)
    }
  }, [create_draft, draft_id, create_article_form, ref_oss, save_draft, toast, username]);

  const [draft_loaded, set_draft_loaded] = useState(false);
  const unique_pwd_ref = useRef<string>('')
  const ref_next_draft_id = useRef<string | undefined>(void 0)
  const [show_confirm_modal_of_pwd, confirm_modal_of_pwd_ctx] = useConfirmModal({
    title: "输入独立密码",
    onOk: () => {
      dispatch({ type: "last_draft_id", payload: ref_next_draft_id.current })
    },
    onCancel: () => {
      unique_pwd_ref.current = '';
      dispatch({ type: "last_draft_id", payload: void 0 })
    },
    children: (props) => (
      <input
        style={{ outline: 'none', border: 'none' }}
        type='text'
        autoFocus
        onChange={e => unique_pwd_ref.current = e.target.value}
        onKeyDown={(e) => {
          if (e.key === 'Escape') props.onCancel?.(e.nativeEvent)
          if (e.key === 'Enter') props.onOk?.(e.nativeEvent)
        }}
      />
    )
  })

  useEffect(() => {
    if (!is_editor_ready || !draft_id) return;
    const pwd = unique_pwd_ref.current;
    set_draft_loaded(false)
    get_draft(draft_id, pwd).then(draft => {
      ref_next_draft_id.current = void 0
      if (ref_ele_input_unique_pwd.current)
        ref_ele_input_unique_pwd.current.value = pwd
      if (ref_ele_input_title.current)
        ref_ele_input_title.current.value = draft?.draft_title || '';
      if (ref_editor.current)
        ref_editor.current.setValue(draft?.draft_content || '');
      ref_draft_detail.current = draft;
      set_draft_loaded(true)
    }).catch(e => {
      dispatch({ type: "last_draft_id", payload: void 0 })
      if (e instanceof PasswordNotMatchError) {
        ref_next_draft_id.current = draft_id;
        show_confirm_modal_of_pwd();
      }
      if (pwd) Toast.show("密码错误！")
    }).finally(() => {
      unique_pwd_ref.current = ''
    })
  }, [dispatch, draft_id, is_editor_ready, get_draft, show_confirm_modal_of_pwd])

  useEffect(() => {
    reload_drafts();
  }, [reload_drafts])

  const topbar_items: ITopBarItem[] = [{
    shortcut: ["shift", 'Escape'],
    show_shrotcut: devices.desktop(),
    disabled: draft_saving || drafts_loading,
    onClick: on_click_exit,
    label: '退出'
  }, {
    shortcut: ["alt", 'P'],
    show_shrotcut: devices.desktop(),
    onClick: on_click_publish_draft_to_article,
    disabled: draft_saving || !draft_loaded || drafts_loading,
    label: '发布'
  }, {
    shortcut: ["ctrl", 's'],
    show_shrotcut: devices.desktop(),
    onClick: on_click_save_current_draft,
    disabled: draft_saving || !draft_loaded || drafts_loading,
    label: '保存',
  }, {
    shortcut: ['alt', 'n'],
    show_shrotcut: devices.desktop(),
    onClick: on_click_create_draft,
    disabled: draft_saving || drafts_loading,
    label: '新建',
  }, {
    shortcut: ['shift', 'Delete'],
    show_shrotcut: devices.desktop(),
    onClick: on_click_remove_current_draft,
    disabled: draft_saving || !draft_id || !draft_loaded || drafts_loading,
    label: '删除',
  }, {
    onClick: () => setDraftsVisible(!draft_visible),
    label: '本地草稿',
  }]

  const oss = ref_oss.current
  const uploader: VditorView.IUploader = async (f) => {
    if (!ref_oss.current) throw new Error(`oss not set, got, ${oss}`)
    const text = await f.text()
    const hash = MD5(text).toString() ?? uuid();
    const result = await ref_oss.current.put("blog/resource/" + hash, f, {
      mime: "image",
      headers: {
        "Content-Type": f.type,
        "Content-Disposition": `attachment;filename="${encodeURIComponent(f.name)}"`,
        "x-oss-meta-username": GlobalStore.value.username,
      }
    })
    return result.url;
  }
  const [upwd_visible, set_upwd_visible] = useState(false);
  const [selected_ids, set_selected_ids] = useState<string[]>();


  const on_items_selected = useCallback((it: IDraftItem[]) => {
    set_selected_ids(it.map(v => v.draft_id))
  }, [])
  return (
    <div className={styles.blog_editor_page}>
      {confirm_modal_of_pwd_ctx}
      {confirm_modal_of_exit_ctx}
      {confirmModalOfDeleteCtx}
      {toastCtx}
      <Show yes={true}>
        <TopBar items={topbar_items} />
      </Show>
      <Show.Div yes={true} className={styles.main_zone}>
        <Show yes={true}>
          <DraftListView
            open={draft_visible}
            onChange={(payload) => dispatch({ type: 'draft_visible', payload })}
            disabled={draft_saving || drafts_loading}
            list={drafts_list}
            actived_draft_id={draft_id}
            selected_draft_ids={selected_ids}
            on_item_actived={open_draft}
            on_click_delete={async (a) => {
              await Promise.all(a.map(v => remove_draft(v.draft_id)))
              await reload_drafts();
              // dispatch({ type: 'last_draft_id', payload: item?.draft_id })
            }}
            on_items_selected={on_items_selected}
          />
        </Show>
        <Show.Div yes={true} className={styles.editor_zone}>
          <Show.Div yes={true} className={styles.article_title_row}>
            <Button
              kind='icon'
              size='l'
              delete={upwd_visible}
              onClick={() => set_upwd_visible(v => !v)}>
              🔒
            </Button>
            <Input
              autoComplete="off"
              name="__"
              ref={ref_ele_input_title}
              placeholder="文章标题"
              autoFocus
              maxLength={64}
              disabled={draft_saving}
              autoTrim
              className={styles.title_input}
              onChange={e => set_is_title_changed(e.target.value.trim() !== ref_draft_detail.current?.draft_title)}
              onBlur={e => set_is_title_changed(e.target.value.trim() !== ref_draft_detail.current?.draft_title)}
            />
            <Show yes={is_title_changed}>
              <Button
                kind='icon'
                size='l'
                onClick={() => {
                  if (ref_ele_input_title.current && ref_draft_detail.current)
                    ref_ele_input_title.current.value = ref_draft_detail.current.draft_title
                  set_is_title_changed(false);
                }}>
                ↶
              </Button>
            </Show>
          </Show.Div>
          <Show.Div yes={upwd_visible} keep>
            <Flex alignItems='center' gap={5}>
              <Text>独立密码</Text>
              <Input
                type="text"
                style={{ flex: 1 }}
                autoComplete="off"
                placeholder="当独立密码丢失，文章内容将无法被读取，请慎重记录独立密码"
                name="_"
                ref={ref_ele_input_unique_pwd}
                disabled={draft_saving}
                autoTrim
                onChange={e => set_is_u_pwd_changed(e.target.value.trim() !== ref_draft_detail.current?.unique_pwd_md5)}
                onBlur={e => set_is_u_pwd_changed(e.target.value.trim() !== ref_draft_detail.current?.unique_pwd_md5)} />
              <Show yes={is_u_pwd_changed}>
                <Button
                  kind='icon'
                  size='s'
                  onClick={() => {
                    if (ref_ele_input_unique_pwd.current && ref_draft_detail.current)
                      ref_ele_input_unique_pwd.current.value = ref_draft_detail.current.unique_pwd_md5
                    set_is_u_pwd_changed(false);
                  }}>
                  ↶
                </Button>
              </Show>
            </Flex>
          </Show.Div >
          <Show yes={true}>
            <VditorView
              uploader={uploader}
              className={styles.editor_wrapper}
              on_ready={v => on_editor_ready(v)} />
          </Show>
        </Show.Div>
      </Show.Div>
    </div >
  )
}
